/*!
  \file connection.cpp
  \author LiuBao
  \date 2011/5/7
  \brief TCP连接类，支持多种数据收发，支持长数据（连续多数据包）收发
 */
#include "connection.h"

#include <QtNetwork>

///  注册Connection::DataScope到Qt元类型
static int _i = qRegisterMetaType<Connection::DataScope>("Connection::DataScope");

static const int TransferTimeout = 30 * 1000;           ///< 连续数据包传输超时（ms）
static const int PongTimeout = 60 * 1000;               ///< Pong回复超时（ms）
static const int PingInterval = 5 * 1000;               ///< Ping间隔（ms）
static const char SeparatorToken = ' ';                 ///< 数据包内容分隔符

Connection::Connection(QObject *parent)
    : QTcpSocket(parent)
{
    greetingMessage = tr("undefined");
    username = tr("unknown");
    state = WaitingForGreeting;
    currentDataType = Undefined;
    currentDataScope = Unknown;
    numBytesForCurrentDataType = -1;
    transferTimerId = 0;
    isGreetingMessageSent = false;
    pingTimer.setInterval(PingInterval);

    connect(this,           SIGNAL(readyRead()),
            this,           SLOT(processReadyRead()));
    connect(this,           SIGNAL(disconnected()),
            &pingTimer,     SLOT(stop()));
    connect(&pingTimer,     SIGNAL(timeout()),
            this,           SLOT(sendPing()));
    connect(this,           SIGNAL(connected()),
            this,           SLOT(sendGreetingMessage()));
}

/*!
  返回由对方Greeting信息解析出的username，详见processReadyRead

  \return 对方用户名@对方地址:对方端口号
 */
QString Connection::name() const
{
    return username;
}

/*!
  设置本客户端用户名，以便生成Greeting信息，对方用于解析username

 \param message 本客户端用户名
 */
void Connection::setGreetingMessage(const QString &message)
{
    greetingMessage = message;
}

/*!
  发送泛型消息

  \param message 待发送的消息，可以是QVariant包装的任意类型
  \return 发送成功返回true；失败返回false
 */
bool Connection::sendMessage(DataScope scope, const QVariant &message)
{
    if (!message.isValid())
        return false;

    QByteArray msg;
    QDataStream ds(&msg, QIODevice::WriteOnly);

    ds << message;

    QByteArray data = QByteArray::number(scope) + ' ' + QByteArray::number(msg.size()) + ' ' + msg;

    return write(data) == data.size();
}

/*!
  连续数据包传输超时触发函数

  \param timerEvent 出发本函数的时间事件
 */
void Connection::timerEvent(QTimerEvent *timerEvent)
{
    /* 连续数据包传输超时 */
    if (timerEvent->timerId() == transferTimerId)
    {
        abort();
        killTimer(transferTimerId);
        transferTimerId = 0;
    }
}

/*!
  读取数据
 */
void Connection::processReadyRead()
{
    /* WaitingForGreeting -> ReadingGreeting */
    if (state == WaitingForGreeting)
    {
        if (!readProtocolHeader())
            return;
        if (currentDataType != Greeting)
        {
            abort();
            return;
        }

        state = ReadingGreeting;
    }

    /* ReadingGreeting -> ReadyForUse */
    if (state == ReadingGreeting)
    {
        if (!hasEnoughData())
            return;

        buffer = read(numBytesForCurrentDataType);
        if (buffer.size() != numBytesForCurrentDataType)
        {
            abort();
            return;
        }

        /* username = 对方用户名@对方地址:对方端口号 */
        username = QString(buffer) + '@' + peerAddress().toString() + ':'
                   + QString::number(peerPort());

        currentDataType = Undefined;        // 数据类型设置为Undefined
        numBytesForCurrentDataType = 0;     // 数据大小清零
        buffer.clear();                     // 清空缓冲区

        if (!isValid())
        {
            abort();
            return;
        }

        if (!isGreetingMessageSent)
            sendGreetingMessage();

        pingTimer.start();
        pongTime.start();

        state = ReadyForUse;
        emit readyForUse();
    }

    /* state == ReadyForUse */
    do
    {
        if (currentDataType == Undefined)
        {
            if (!readProtocolHeader())
                return;
        }
        if (!hasEnoughData())               // read缓冲区数据量不足，直接返回等待下一数据包
            return;

        processData();
    } while (bytesAvailable() > 0);
}

/*!
  周期性发送Ping
 */
void Connection::sendPing()
{
    /* 若本次sendPing距离最近一次Pong时间超过PongTimeout，即Pong相应超时 */
    if (pongTime.elapsed() > PongTimeout)
    {
        abort();
        return;
    }

    write("PING 1 p");
}

/*!
  发送Greeting信息，并更新isGreetingMessageSent
 */
void Connection::sendGreetingMessage()
{
    QByteArray greeting = greetingMessage.toUtf8();
    QByteArray data = "GREETING " + QByteArray::number(greeting.size()) + ' ' + greeting;
    if (write(data) == data.size())
        isGreetingMessageSent = true;
}

/*!
  从数据中读取到SeparatorToken位置，并拷贝到buffer

  \param maxSize 读取最大数据量
  \return 缓冲区的剩余空间
 */
int Connection::readDataIntoBuffer(int maxSize)
{
    /* 默认maxSize == MaxBufferSize */
    if (maxSize > MaxBufferSize)
        return 0;

    int numBytesBeforeRead = buffer.size();

    /* 若buffer已满 */
    if (numBytesBeforeRead == MaxBufferSize)
    {
        abort();
        return 0;
    }

    /* 读取SeparatorToken（包含）前的数据到buffer */
    while (bytesAvailable() > 0 && buffer.size() < maxSize)
    {
        buffer.append(read(1));
        if (buffer.endsWith(SeparatorToken))
            break;
    }

    /* 返回读取到buffer的字节数 */
    return buffer.size() - numBytesBeforeRead;
}

/*!
  从数据头中解析出数据长度，通常需要在调用一次readDataIntoBuffer后（读取数据类型）

  \return 解析出的数据长度
 */
int Connection::dataLengthForCurrentDataType()
{
    if (bytesAvailable() <= 0                       // 无可读数据
            || readDataIntoBuffer() <= 0            // 读取数据中长度信息（由SeparatorToken分割的第二部分）到buffer失败
            || !buffer.endsWith(SeparatorToken))    // 缓冲区不以SeparatorToken结尾
        return 0;

    buffer.chop(1);                                 // 去掉buffer末尾的SeparatorToken
    int number = buffer.toInt();                    // 把字符串转为int，得到数据长度
    buffer.clear();

    return number;
}

/*!
  读取数据头部，包括数据类型（Ping/Pong/PlainText/Greeting）和
  数据长度，更新数据类型到currentDataType、数据长度到numBytesForCurrentDataType

  \return 读取成功返回true；否则返回false
 */
bool Connection::readProtocolHeader()
{
    if (transferTimerId)
    {
        killTimer(transferTimerId);
        transferTimerId = 0;
    }

    /* 若读取数据失败（已读数据量为 <= 0） */
    if (readDataIntoBuffer() <= 0)
    {
        transferTimerId = startTimer(TransferTimeout);
        return false;
    }

    if (buffer == "PING ")
        currentDataType = Ping;
    else if (buffer == "PONG ")
        currentDataType = Pong;
    else if (buffer == "GREETING ")
        currentDataType = Greeting;
    else
    {
        bool isMessage = false;

        currentDataScope = static_cast<DataScope>(buffer.trimmed().toInt(&isMessage));
        if (isMessage)
            currentDataType = Message;
        else
        {
            currentDataType = Undefined;
            abort();
            return false;
        }
    }

    buffer.clear();// 清除已读入buffer的数据头
    numBytesForCurrentDataType = dataLengthForCurrentDataType();
    return true;
}

/*!
  判断read缓冲区（由QAbstractSocket内部维护）中数据量是否达到数据包头标识的大小

  \return read缓冲区数据量已达到数据包头标识大小，返回ture；否则返回false
 */
bool Connection::hasEnoughData()
{
    if (transferTimerId)
    {
        killTimer(transferTimerId);
        transferTimerId = 0;
    }

    if (numBytesForCurrentDataType <= 0)
        numBytesForCurrentDataType = dataLengthForCurrentDataType();

    if (bytesAvailable() < numBytesForCurrentDataType // 当前read缓冲区未包含全部数据时
            || numBytesForCurrentDataType <= 0)       // 或者当前数据包标识大小 <= 0（无效数据包）
    {
        /* 启动传输定时器，等待后续数据包到来 */
        transferTimerId = startTimer(TransferTimeout);
        return false;
    }

    return true;
}

/*!
  根据数据类型处理数据，不包含Greeting类型数据（已经在processReadyRead中处理完毕）
 */
void Connection::processData()
{
    buffer = read(numBytesForCurrentDataType);

    /* 缓冲区数据长度与标识不一致 */
    if (buffer.size() != numBytesForCurrentDataType)
    {
        abort();                            // 立即丢弃数据包，重置socket
        return;
    }

    switch (currentDataType)
    {
    case Message:
    {
        QDataStream ds(&buffer, QIODevice::ReadOnly);
        QVariant message;

        ds >> message;

        emit newMessage(currentDataScope, username, message);
        break;
    }
    case Ping:
        write("PONG 1 p");                  // 立即回复Pong
        break;
    case Pong:
        pongTime.restart();                 // Pong计时器重置
        break;
    default:
        break;
    }

    currentDataType = Undefined;            // 数据类型设置为Undefined
    currentDataScope = Unknown;             // 数据域置为Unknown
    numBytesForCurrentDataType = 0;         // 数据大小清零
    buffer.clear();                         // 清空缓冲区
}
